# -*- encoding: utf-8 -*-

# Copyright 2010 István Szentandrási
# <etude-music-player@googlegroups.com>

# This file is part of Etude.
#
# Etude is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as 
# published by the Free Software Foundation, either version 3 
# of the License, or (at your option) any later version.
#
# Etude is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Etude. If not, see <http://www.gnu.org/licenses/>.

import gc
import gobject
import gtk
import os
import sys
import xmmsclient
import xmmsclient.glib

from bindata import BinData
from collections import Collection
from config import Config
from medialib import MediaLib
from playback import Playback
from playlist import Playlist
from plugins import Plugins
from common import *


def check_disconnected():
    """
    just a decorator to check if the client is disconnected
    in that case return False -> this means xmms callback won't restart
    pretty desperate solution, but I couldn't find a way to 
    manually disconnect from xmms broadcast messages
    Until then the existing connection wouldn't go away
    """
    def _check_disconnected(function):
        def __check_disconnected(*args, **kw):
            #first argument class member
            if args[0]._disconnected:
                return False
            return function(*args, **kw)
        return __check_disconnected
    return _check_disconnected

class MClient(gobject.GObject):
    """
    Main class communicating with the daemon
    singleton class
    """

    def __new__(cls, *args, **kw):
        if not hasattr(cls, '_instance'):
            orig = super(MClient, cls)
            cls._instance = orig.__new__(cls, *args, **kw)
        return cls._instance
        #ob = super(MClient, cls).__new__(cls, *args, **kw)
        #return ob
    
    def __init__(self):
        if hasattr(self, '_inited'):
            return
        else:
            self._inited = True
        self.__gobject_init__()
        self.xmms = None
        self.xmms_slow = None
        self.xmms_fast = None
        self.playback = Playback(self)
        self.playlist = Playlist(self)
        self.medialib = MediaLib(self)
        self.plugins = Plugins(self)
        self.config = Config(self)
        self.bindata = BinData(self)
        self.collection = Collection(self)
        self._collections = {}
        self._disconnected = True

    
    @check_disconnected()
    def quit(self, *args, **kw):
        """
        quit the client - this means setting the _disconnected flag to true
        This is necessary, as the variable for the xmms connection is stored 
        in some of the results returned by the server, and there is no real 
        destructor in python, so if the _disconnected is set to True all the 
        broadcast signals will be blocked and return True which will tell the 
        server to stop sending those signals
        """
        if 'cb' in kw:
            self._disconnected = True
            kw['cb'](*args)
            return
        gtk.main_quit()
        sys.exit(1)

    def get_collections(self, namespace):
        """
        Return the collections according to namespace.
        Not safe to use during inicialization.
        """
        try:
            return self._collections[namespace]
        except:
            return []

    def xmms_connect(self, url = '', exit_cb = None):
        """
        connect to an xmms server
        We create two asynchronous connections - one for normal usage
        and a one for slower operations like storing albumart
        """
        try:
            self.xmms_slow = xmmsclient.XMMS("Etude")
            self.xmms_fast = xmmsclient.XMMS("Etude")
            self.xmms = self.xmms_slow
            if exit_cb == None:
                cb = self.quit
            else:
                cb = lambda xmms: self.quit(xmms, cb = exit_cb)

            if url == '':
                url = os.getenv('XMMS_PATH')
            self.xmms.connect(url, cb)
            self.xmms_fast.connect(url, cb)
            self.glibc = xmmsclient.glib.GLibConnector(self.xmms_slow)
            self.glibc_fast = xmmsclient.glib.GLibConnector(self.xmms_fast)
            self._disconnected = False
        except IOError:
            self.xmms = None
            self.xmms_slow = None
            self.xmms_fast = None
            self._disconnected = True
            return False
        return True

    def xmms_disconnect(self):
        """
        Should call this function when this connection is not needed - when switching
        profiles
        """
        try:
            del self.xmms
            del self.xmms_slow
            del self.xmms_fast
            del self.glibc
            del self.glibc_fast
        except AttributeError:
            pass
        gc.collect()
        self.xmms = None
        self.xmms_slow = None
        self.xmms_fast = None
        self._disconnected = True

    def register_cbs(self):
        """
        register callbacks
        """
        self.playback.current_id(self.e_playback_current_id)
        self.xmms_fast.playback_status(self.e_playback_status)
        self.playback.volume_get()

        self.xmms_fast.broadcast_collection_changed(self.e_collection_changed)
        try:
            self.xmms_fast.broadcast_configval_changed(self.e_configval_changed)
        except AttributeError: #this is called different in the git
            self.xmms_fast.broadcast_config_value_changed(self.e_configval_changed)
        self.xmms_fast.broadcast_mediainfo_reader_status(self.e_mediainfo_reader_status)
        self.xmms_fast.broadcast_medialib_entry_added(self.e_medialib_entry_added)
        self.xmms_fast.broadcast_medialib_entry_changed(self.e_medialib_entry_changed)
        self.xmms_fast.broadcast_playback_current_id(self.e_playback_current_id)
        self.xmms_fast.broadcast_playback_status(self.e_playback_status)
        self.xmms_fast.broadcast_playback_volume_changed(self.e_playback_volume_changed)
        self.xmms_fast.broadcast_playlist_changed(self.e_playlist_changed)
        self.xmms_fast.broadcast_playlist_current_pos(self.e_playlist_current_pos)
        self.xmms_fast.broadcast_playlist_loaded(self.e_playlist_loaded)
        self.xmms_fast.signal_playback_playtime(self.playtime_cb)

        #store the list of collections locally
        cl = self.collection
        cl.list(namespace = "Playlists", cb = self.__list_cl, cb_args = {'ns': "Playlists"}, fast=True, wait=True)
        cl.list(namespace = "Collections", cb = self.__list_cl, cb_args = {'ns': "Collections"}, fast=True, wait=True)

        self.config.load()
        self.plugins.load()

    def __list_cl(self, array, ns = None):
        self._collections[ns] = [col for col in array if not col.startswith("_")]

    @check_disconnected()
    @check_result_error()
    def playtime_cb(self, result):
        """
        signal containing the current playtime
        """
        self.playback._playtime_cb(result)
        return True
    
    @check_disconnected()
    @check_result_error()
    def e_collection_changed(self, result):
        """
        when a collection changed emit the gobject signal and update the local list
        """
        hash = result.value()
        try:
            ns = hash[u'namespace']
            name = hash[u'name']
            if hash[u'type'] == xmmsclient.COLLECTION_CHANGED_UPDATE:
                #this is most frequent, we shouldn't check all in this case
                pass
            elif hash[u'type'] == xmmsclient.COLLECTION_CHANGED_ADD:
                if name not in self._collections[ns]:
                    self._collections[ns].append(name)
            elif hash[u'type'] == xmmsclient.COLLECTION_CHANGED_REMOVE:
                try:
                    self._collections[ns].remove(name)
                except ValueError:
                    pass
            elif hash[u'type'] == xmmsclient.COLLECTION_CHANGED_RENAME:
                try:
                    self._collections[ns].remove(name)
                except ValueError:
                    pass
                self._collections[ns].append(hash[u'newname'])
        except KeyError:
            pass
        self.emit("e_collection_changed", result.value())

    @check_disconnected()
    @check_result_error()
    def e_configval_changed(self, result):
        """
        configuration value changed
        print("configval changed")
        print(result.value())
        """
        for key, value in result.value().items():
            self.emit("e_configval_changed", key, value)

    @check_disconnected()
    @check_result_error()
    def e_mediainfo_reader_status(self, result):
        """
        when the daemon starts reading metadata from the file, or stops
        sends this signal (result.value() = 1 (starts), 0 (stops))
        """
        self.emit('e_mediainfo_reader_status', result.value())


    @check_disconnected()
    @check_result_error()
    def e_medialib_entry_added(self, result):
        """
        new song added to the media library
        """
        #print("entry added")
        self.emit('e_medialib_entry_added', result.value())
    
    @check_disconnected()
    @check_result_error()
    def e_medialib_entry_changed(self, result):
        """
        Info stored in the medialib about a song changed,
        if it's the current song, we set the playback.current_info
        accordingly
        """
        #print("entry changed")
        id = result.value()
        try:
            if id == self.playback.current_info['id']:
               self.playback._set_current_id(id)
               return
        except KeyError:
            pass
        self.emit('e_medialib_entry_changed', id)
        return

    @check_disconnected()
    @check_result_error()
    def e_playback_current_id(self, result):
        """
        the current playing song changed
        """
        self.playback._set_current_id(result.value())

    @check_disconnected()
    @check_result_error()
    def e_playback_status(self, result):
        """
        the playback stopped/startet/paused
        """
        val = result.value()
        self.emit("e_playback_status", val)

    @check_disconnected()
    @check_result_error()
    def e_playback_volume_changed(self, result):
        """
        the volume changed
        """
        self.playback._volume_get_cb(result)

    @check_disconnected()
    @check_result_error()
    def e_playlist_changed(self, result):
        """
        a playlist has been updated
        """
        self.emit("e_playlist_changed", result.value())

    @check_disconnected()
    @check_result_error()
    def e_playlist_current_pos(self, result):
        """
        the current position in the playlist changed
        """
        self.emit("e_playlist_current_pos", result.value())

    @check_disconnected()
    @check_result_error()
    def e_playlist_loaded(self, result):
        """
        a playlist was loaded/set as active
        """
        self.emit("e_playlist_loaded", result.value())


gobject.type_register(MClient)
#gobject.signal_new("e_play", MClient, gobject.SIGNAL_RUN_LAST, gobject.TYPE_BOOLEAN, () )
gobject.signal_new("e_collection_changed", MClient, gobject.SIGNAL_RUN_LAST, 
        gobject.TYPE_BOOLEAN, (gobject.TYPE_PYOBJECT,) )
#collection changed ( dictionary containing information about the change)

gobject.signal_new("e_configval_changed", MClient, gobject.SIGNAL_RUN_LAST, 
        gobject.TYPE_BOOLEAN, (gobject.TYPE_STRING, gobject.TYPE_STRING) )
#configuration value changed ( configuration_key, value )

gobject.signal_new("e_mediainfo_reader_status", MClient, gobject.SIGNAL_RUN_LAST, 
        gobject.TYPE_BOOLEAN, (gobject.TYPE_INT,) )
#see MClient.e_mediainfo_reader_status

gobject.signal_new("e_medialib_entry_added", MClient, gobject.SIGNAL_RUN_LAST, 
        gobject.TYPE_BOOLEAN, (gobject.TYPE_INT,) )
#a song was added to medialib (the id of the new song)

gobject.signal_new("e_medialib_entry_changed", MClient, gobject.SIGNAL_RUN_LAST,
        gobject.TYPE_BOOLEAN, (gobject.TYPE_INT,) )
#medialib information about the song changed (the id of the song)

gobject.signal_new("e_playback_current_id", MClient, gobject.SIGNAL_RUN_LAST, 
        gobject.TYPE_BOOLEAN, (gobject.TYPE_INT,) )
#the current song (medialib_id)

gobject.signal_new("e_playback_status", MClient, gobject.SIGNAL_RUN_LAST,
        gobject.TYPE_BOOLEAN, (gobject.TYPE_INT,) )
# playback was stopped/started/paused (on of PB_* constants)

gobject.signal_new("e_playback_volume_changed", MClient, gobject.SIGNAL_RUN_LAST,
        gobject.TYPE_BOOLEAN, (gobject.TYPE_PYOBJECT,) )
#the volume changed, ( {'channel': value } )

gobject.signal_new("e_playback_playtime", MClient, gobject.SIGNAL_RUN_LAST, 
        gobject.TYPE_BOOLEAN, (gobject.TYPE_INT,) )
#the playtime in milliseconds ( mseconds)

gobject.signal_new("e_playlist_changed", MClient, gobject.SIGNAL_RUN_LAST, 
        gobject.TYPE_BOOLEAN, (gobject.TYPE_PYOBJECT,) )
#playlist changed - Added, removed id, sorted, shuffled etc

gobject.signal_new("e_playlist_repeat_changed", MClient, gobject.SIGNAL_RUN_LAST, 
        gobject.TYPE_BOOLEAN, (gobject.TYPE_INT,) )
# ( one of PLAYLIST_REPEAT_* constants )

gobject.signal_new("e_playlist_current_pos", MClient, gobject.SIGNAL_RUN_LAST,
        gobject.TYPE_BOOLEAN, (gobject.TYPE_PYOBJECT,) )
# the current position in the playlist ( {'name': '<playlisnt_name>', 
#   'position': '<position>'})

gobject.signal_new("e_playlist_loaded", MClient, gobject.SIGNAL_RUN_LAST,
        gobject.TYPE_BOOLEAN, (gobject.TYPE_STRING,) )
# (name of the activo playlist)

gobject.signal_new("e_plugin_loaded", MClient, gobject.SIGNAL_RUN_LAST,
        gobject.TYPE_BOOLEAN, (gobject.TYPE_STRING,))
# a supported xmms plugin loaded

gobject.signal_new("e_plugin_unloaded", MClient, gobject.SIGNAL_RUN_LAST, 
        gobject.TYPE_BOOLEAN, (gobject.TYPE_STRING,))
# a supported xmms plugin unloaded

gobject.signal_new("e_disconnected", MClient, gobject.SIGNAL_RUN_LAST, 
        gobject.TYPE_BOOLEAN, (gobject.TYPE_STRING,))
#disconnected signal - don't use this :P

gobject.signal_new("e_result_error", MClient, gobject.SIGNAL_RUN_LAST, 
        gobject.TYPE_BOOLEAN, (gobject.TYPE_STRING,))
#an unchecked error happened - use for logging



